package com.codemeta.bokit.comm.kit;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.codemeta.bokit.comm.exception.CommException;
import com.codemeta.bokit.comm.global.DefaultErrorCode;
import com.codemeta.bokit.comm.global.ErrorCode;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * 异常工具类
 *
 * @author 美萌hecc
 * @date 2020/6/23
 */
@Slf4j
public class ExceptionKit {

    /**
     * 错误码提示模板
     */
    private static final ConcurrentMap<String, String> MESSAGES = new ConcurrentHashMap<>();

    public static void put(String code, String message) {
        MESSAGES.put(code, message);
    }

    public static void putAll(Map<String, String> messages) {
        MESSAGES.putAll(messages);
    }

    public static void delete(String code, String message) {
        MESSAGES.remove(code, message);
    }

    static {
        // 加载默认错误码
        for (DefaultErrorCode error : DefaultErrorCode.values()) {
            MESSAGES.put(error.getCode(), error.getMessage());
        }
        // 加载所有@component错误码实现类
        SpringUtil.getBeansOfType(ErrorCode.class)
            .forEach((key, val) -> MESSAGES.put(val.getCode(), val.getMessage()));
        log.debug("缓存错误码总数：{}", MESSAGES.size());
    }

    public static CommException exception(ErrorCode error) {
        String message = MESSAGES.getOrDefault(error.getCode(), error.getMessage());
        return exception(error, message);
    }

    public static CommException exception(ErrorCode error, Object... params) {
        String template = StrUtil.blankToDefault(MESSAGES.get(error.getCode()), "{}");
        return exception(error, template, params);
    }

    public static CommException exception(ErrorCode error, String template, Object... params) {
        String message = StrUtil.format(StrUtil.blankToDefault(template, "{}"), params);
        return new CommException(message, error);
    }

    /**
     * Build an error message containing the message of each throwable in the chain.
     *
     * <p>Throwable messages are separated by new lines.
     *
     * @param throwable the top level error.
     * @return the error message.
     */
    public static String buildErrorMessage(final Throwable throwable) {
        if (throwable == null) {
            return "";
        }
        final List<String> messages = new ArrayList<>(new LinkedHashSet<>(getErrorMessages(throwable)));
        final String msg = messages.remove(0);
        final String causeMsg = messages.stream().filter(s -> !s.isEmpty())
            .collect(Collectors.joining(System.lineSeparator()));
        return causeMsg.isEmpty() ? msg : msg + System.lineSeparator() + causeMsg;
    }

    /**
     * Build a list containing the error message for each throwable in the chain.
     *
     * @param e the top level error.
     * @return the list of error messages.
     */
    public static List<String> getErrorMessages(final Throwable e) {
        return getCauses(e).stream().map(ExceptionKit::getErrorMessage).collect(Collectors.toList());
    }

    private static String getErrorMessage(final Throwable e) {
        if (e instanceof ConnectException) {
            return "Could not connect to the server. "
                + "Please check the server details are correct and that the server is running.";
        } else {
            return e.getMessage() == null ? e.toString() : e.getMessage();
        }
    }

    private static List<Throwable> getCauses(final Throwable e) {
        final List<Throwable> list = new ArrayList<>();
        Throwable cause = e;
        while (cause != null && !list.contains(cause)) {
            list.add(cause);
            cause = cause.getCause();
        }
        return list;
    }

}